package com.mega.fe.util;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field; 

public class MyUnsafe {
    public static final int ADDRESS_SIZE = 4;

    public static final int ARRAY_BOOLEAN_BASE_OFFSET;

    static {
        try {
            ARRAY_BOOLEAN_BASE_OFFSET = arrayBaseOffset(boolean[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(byte[].class)} */
    public static final int ARRAY_BYTE_BASE_OFFSET;

    static {
        try {
            ARRAY_BYTE_BASE_OFFSET = arrayBaseOffset(byte[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(short[].class)} */
    public static final int ARRAY_SHORT_BASE_OFFSET;

    static {
        try {
            ARRAY_SHORT_BASE_OFFSET = arrayBaseOffset(short[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(char[].class)} */
    public static final int ARRAY_CHAR_BASE_OFFSET;

    static {
        try {
            ARRAY_CHAR_BASE_OFFSET = arrayBaseOffset(char[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(int[].class)} */
    public static final int ARRAY_INT_BASE_OFFSET;

    static {
        try {
            ARRAY_INT_BASE_OFFSET = arrayBaseOffset(int[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(long[].class)} */
    public static final int ARRAY_LONG_BASE_OFFSET;

    static {
        try {
            ARRAY_LONG_BASE_OFFSET = arrayBaseOffset(long[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(float[].class)} */
    public static final int ARRAY_FLOAT_BASE_OFFSET;

    static {
        try {
            ARRAY_FLOAT_BASE_OFFSET = arrayBaseOffset(float[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(double[].class)} */
    public static final int ARRAY_DOUBLE_BASE_OFFSET;

    static {
        try {
            ARRAY_DOUBLE_BASE_OFFSET = arrayBaseOffset(double[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /** The value of {@code arrayBaseOffset(Object[].class)} */
    public static final int ARRAY_OBJECT_BASE_OFFSET;

    static {
        try {
            ARRAY_OBJECT_BASE_OFFSET = arrayBaseOffset(Object[].class);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static  int getInt(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getInt", MethodType.methodType(int.class, Object.class, long.class));
        return (int) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    public static int     getIntVolatile(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getIntVolatile", MethodType.methodType(int.class, Object.class, long.class));
        return (int) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);

    }

    /**
     * Stores a value into a given Java variable.
     * <p>
     * The first two parameters are interpreted exactly as with
     * {@link #getInt(Object, long)} to refer to a specific
     * Java variable (field or array element).  The given value
     * is stored into that variable.
     * <p>
     * The variable must be of the same type as the method
     * parameter {@code x}.
     *
     * @param o Java heap object in which the variable resides, if any, else
     *        null
     * @param offset indication of where the variable resides in a Java heap
     *        object, if any, else a memory address locating the variable
     *        statically
     * @param x the value to store into the indicated Java variable
     * @throws RuntimeException No defined exceptions are thrown, not even
     *         {@link NullPointerException}
     */
    
    public static void putInt(Object o, long offset, int x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putInt", MethodType.methodType(void.class, Object.class, long.class, int.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);

    }

    public static void    putIntVolatile(Object o, long offset, int x) throws Throwable{
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putIntVolatile", MethodType.methodType(void.class, Object.class, long.class, int.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);

    }

    /**
     * Fetches a reference value from a given Java variable.
     * @see #getInt(Object, long)
     */
    
    public static Object getReference(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getReference", MethodType.methodType(Object.class, Object.class, long.class));
        return methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);

    }

    /**
     * Stores a reference value into a given Java variable.
     * <p>
     * Unless the reference {@code x} being stored is either null
     * or matches the field type, the results are undefined.
     * If the reference {@code o} is non-null, card marks or
     * other store barriers for that object (if the VM requires them)
     * are updated.
     * @see #putInt(Object, long, int)
     */
    
    public static void putReference(Object o, long offset, Object x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putReference", MethodType.methodType(void.class, Object.class, long.class, Object.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static boolean getBoolean(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getBoolean", MethodType.methodType(boolean.class, Object.class, long.class));
        return (boolean) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putBoolean(Object o, long offset, boolean x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putBoolean", MethodType.methodType(void.class, Object.class, long.class, boolean.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static byte    getByte(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getByte", MethodType.methodType(byte.class, Object.class, long.class));
        return (byte) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putByte(Object o, long offset, byte x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putByte", MethodType.methodType(void.class, Object.class, long.class, byte.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static short   getShort(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getShort", MethodType.methodType(short.class, Object.class, long.class));
        return (short) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putShort(Object o, long offset, short x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putShort", MethodType.methodType(void.class, Object.class, long.class, short.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static char    getChar(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getChar", MethodType.methodType(char.class, Object.class, long.class));
        return (char) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putChar(Object o, long offset, char x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putChar", MethodType.methodType(void.class, Object.class, long.class, char.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static long    getLong(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getLong", MethodType.methodType(long.class, Object.class, long.class));
        return (long) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putLong(Object o, long offset, long x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putLong", MethodType.methodType(void.class, Object.class, long.class, long.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static float   getFloat(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getFloat", MethodType.methodType(float.class, Object.class, long.class));
        return (float) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putFloat(Object o, long offset, float x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putFloat", MethodType.methodType(void.class, Object.class, long.class, float.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    /** @see #getInt(Object, long) */
    
    public static double  getDouble(Object o, long offset) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "getDouble", MethodType.methodType(double.class, Object.class, long.class));
        return (double) methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset);
    }

    /** @see #putInt(Object, long, int) */
    
    public static void    putDouble(Object o, long offset, double x) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "putDouble", MethodType.methodType(void.class, Object.class, long.class, double.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(o, offset, x);
    }

    public static Object getObject(Object o, long offset) throws Throwable {
        return getReference(o, offset);
    }

    public static void putObject(Object o, long offset, Object x) throws Throwable {
        putReference(o, offset, x);
    }

    public static void ensureClassInitialized(Class<?> c) throws  Throwable {
        if (c == null) {
            throw new NullPointerException();
        }

        ensureClassInitialized0(c);
    }

    public static void ensureClassInitialized0(Class<?> c) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "ensureClassInitialized0", MethodType.methodType(void.class, Class.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(c);
    }

    public static Object allocateInstance(Class<?> cls) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "allocateInstance", MethodType.methodType(Object.class, Class.class));
        return methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(cls);
    }

    public static long staticFieldOffset(Field f) throws Throwable{
        if (f == null) {
            throw new NullPointerException();
        }

        return staticFieldOffset0(f);
    }

    public static long objectFieldOffset(Field f) throws Throwable {
        if (f == null) {
            throw new NullPointerException();
        }

        return objectFieldOffset0(f);
    }

    public static long objectFieldOffset0(Field f) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "objectFieldOffset0", MethodType.methodType(long.class, Field.class));
        return (long)methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(f);

    }
    public static long staticFieldOffset0(Field f) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "staticFieldOffset0", MethodType.methodType(long.class, Field.class));
        return (long)methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(f);

    }

    public static int arrayBaseOffset(Class<?> arrayClass) throws Throwable {
        if (arrayClass == null) {
            throw new NullPointerException();
        }

        return arrayBaseOffset0(arrayClass);
    }

    public static int arrayBaseOffset0(Class<?> arrayClass) throws  Throwable{
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "arrayBaseOffset0", MethodType.methodType(int.class, Class.class));
        return (int)methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(arrayClass);

    }

    public static long getAddress(Object o, long offset) throws Throwable {
        if (ADDRESS_SIZE == 4) {
            return Integer.toUnsignedLong(getInt(o, offset));
        } else {
            return getLong(o, offset);
        }
    }

    public static void copyMemory(Object srcBase, long srcOffset,
                           Object destBase, long destOffset,
                           long bytes) throws Throwable {
        copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes);

        if (bytes == 0) {
            return;
        }

        copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes);
    }

    public static void copyMemoryChecks(Object srcBase, long srcOffset,
                                  Object destBase, long destOffset,
                                  long bytes) {
        checkSize(bytes);
        checkPrimitivePointer(srcBase, srcOffset);
        checkPrimitivePointer(destBase, destOffset);
    }

    public static void checkSize(long size) {
        if (ADDRESS_SIZE == 4) {
            // Note: this will also check for negative sizes
            if (!is32BitClean(size)) {
                throw invalidInput();
            }
        } else if (size < 0) {
            throw invalidInput();
        }
    }

    public static void checkPrimitivePointer(Object o, long offset) {
        checkPointer(o, offset);

        if (o != null) {
            // If on heap, it must be a primitive array
            checkPrimitiveArray(o.getClass());
        }
    }

    public static void checkPointer(Object o, long offset) {
        if (o == null) {
            checkNativeAddress(offset);
        } else {
            checkOffset(o, offset);
        }
    }

    public static void checkNativeAddress(long address) {
        if (ADDRESS_SIZE == 4) {
            // Accept both zero and sign extended pointers. A valid
            // pointer will, after the +1 below, either have produced
            // the value 0x0 or 0x1. Masking off the low bit allows
            // for testing against 0.
            if ((((address >> 32) + 1) & ~1) != 0) {
                throw invalidInput();
            }
        }
    }

    public static RuntimeException invalidInput() {
        return new IllegalArgumentException();
    }

    public static void checkPrimitiveArray(Class<?> c) {
        Class<?> componentType = c.getComponentType();
        if (componentType == null || !componentType.isPrimitive()) {
            throw invalidInput();
        }
    }

    public static void checkOffset(Object o, long offset) {
        if (ADDRESS_SIZE == 4) {
            // Note: this will also check for negative offsets
            if (!is32BitClean(offset)) {
                throw invalidInput();
            }
        } else if (offset < 0) {
            throw invalidInput();
        }
    }

    public static boolean is32BitClean(long value) {
        return value >>> 32 == 0;
    }

    public static void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) throws Throwable {
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "copyMemory0", MethodType.methodType(void.class, Object.class, long.class, Object.class, long.class, long.class));
        methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(srcBase ,srcOffset, destBase, destOffset, bytes);
    }

    public static long alignToHeapWordSize(long bytes) {
        if (bytes >= 0) {
            return (bytes + ADDRESS_SIZE - 1) & ~(ADDRESS_SIZE - 1);
        } else {
            throw invalidInput();
        }
    }

    public static void allocateMemoryChecks(long bytes) {
        checkSize(bytes);
    }

    public static long allocateMemory(long bytes) throws Throwable {
        bytes = alignToHeapWordSize(bytes);

        allocateMemoryChecks(bytes);

        if (bytes == 0) {
            return 0;
        }

        long p = allocateMemory0(bytes);
        if (p == 0) {
            throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes");
        }

        return p;
    }

    public static long allocateMemory0(long bytes) throws Throwable{
        MethodHandle methodHandle = MEGA.IMPL_LOOKUP().findVirtual(UnsafeUtil.INTERNAL_UNSAFE_CLASS(), "allocateMemory0", MethodType.methodType(long.class, long.class));
        return (long)methodHandle.bindTo(UnsafeUtil.INTERNAL_UNSAFE_INSTANCE()).invoke(bytes);

    }
}
